#les packages a installer
#install.packages(ggplot2)
#install.packages(dplyr)
#install.packages(tidyr)
#install.packages(arules)
#install.packages(fastDummies)
#install.packages(plotly)
#install.packages(stringr)
setwd("C:/Users/utilisateur/Desktop/data_mining_project")
directory <- "./dataset"
files <- list.files(directory, pattern = ".csv")
data_list <- lapply(files, function(file) read.csv(file.path(directory, file)))
mydata <- do.call(rbind, data_list)
mydata
library(tidyr)
library(fastDummies)
mydata$gross.in... <- NULL
mydata$star_id <- NULL
mydata$director_id <- NULL
mydata$description <- NULL
#We delete the movies without rating
mydata <- mydata[complete.cases(mydata$rating), ]
#We delete the movies without runtime
mydata <- mydata[complete.cases(mydata$runtime), ]
#We delete the movies without votes
mydata <- mydata[complete.cases(mydata$votes), ]
# to be sure we delete every NA value in the dataset
mydata <- na.omit(mydata)
#we delete all the duplicated movies in the dataset
mydata <- unique(mydata, by = "movie_id")
# Supprimer le texte "min" de la colonne "runtime"
mydata$runtime <- gsub("min", "", mydata$runtime)
# Convertir la colonne "runtime" en type numérique
mydata$runtime <- as.numeric(mydata$runtime)
Avis : NAs introduced by coercion
# Convertir la colonne "year" en type numérique
mydata$year <- as.numeric(mydata$year)
Avis : NAs introduced by coercion
colSums(is.na(mydata))
movie_id movie_name year certificate runtime genre rating director
0 0 15 0 9093 0 0 0
star votes
0 0
# utiliser separate() pour créer une colonne pour chaque valeur dans "genres"
mydata <- mydata %>% separate_rows(genre, sep = ", ") %>%
pivot_wider(names_from = genre, values_from = genre, values_fn = length, values_fill = 0)
mydata
mydata <- dummy_cols(mydata, select_columns = "certificate")
mydata$certificate <- NULL
mydata$star <- NULL
mydata
library(ggplot2)
library(dplyr)
# Compter le nombre de films par rating
film_per_rating <- mydata %>%
group_by(rating) %>%
summarize(n = n())
# Tracer un histogramme du rating des films
ggplot(data = film_per_rating, aes(x = rating, y = n)) +
geom_bar(stat = "identity", color = "red", fill = "blue", alpha = 0.5) +
labs(title = "Histogramme du rating des films", x = "Rating", y = "Nombre de films")

on remarqe que nos données ont une distribution normale centrée sur
la valeur 6, cela veut dire qu’il y’a plus de probalibité pour un futur
film qu’il soit noté 6/10 que autre chose.
de la on peu considérer qu’un film qui a un (RATING >=8) est un
film reussi et (RATING <=4) est un echec
#on prends une copie de la dataframe (pour analyser les artists)
mydata$movie_name <- NULL
mydata$movie_id <- NULL
mydata$director <- NULL
# installer et charger la librairie arules
library(arules)
# créer une matrice binaire de transactions à partir de la dataframe
transactions_mat <- as.matrix(mydata)
# remplacer les valeurs manquantes par 0
transactions_mat[is.na(transactions_mat)] <- 0
transactions_mat[transactions_mat > 0] <- 1
# convertir la matrice de transactions en un objet "transactions" de la librairie arules
transactions_obj <- as(transactions_mat, "transactions")
# appliquer la fonction apriori pour extraire les règles d'association
rules <- apriori(transactions_obj, parameter = list(support = 0.1, confidence = 0.5, minlen = 2))
Apriori
Parameter specification:
Algorithmic control:
Absolute minimum support count: 11353
set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[72 item(s), 113533 transaction(s)] done [0.04s].
sorting and recoding items ... [14 item(s)] done [0.01s].
creating transaction tree ... done [0.05s].
checking subsets of size 1 2 3 4 5 6 done [0.00s].
writing ... [671 rule(s)] done [0.00s].
creating S4 object ... done [0.01s].
# afficher les règles d'association
inspect(rules)
d’apres les resultat de l’algorithme apriori on voit qu’il y’a une
relation forte entre {rating} et {year/runtime/votes} ainsi que les
differents genre qu’on dans la dataframe cela est vu grace au mesure
(support/confiance/lift) (plus leur valeurs est élevé plus la relation
est forte)
-Le support (ou soutien) est une mesure de la fréquence d’apparition
d’un itemset dans un ensemble de transactions
-La confiance est une mesure de la probabilité conditionnelle
d’observer le conséquent d’une règle sachant que l’antécédent est
présent
-Le lift est une mesure de l’association entre l’antécédent et le
conséquent d’une règle
support confidence coverage lift
year => rating 0.9998679 1.0000000 0.9998679 1.0000000
runtime => rating 0.9199087 1.0000000 0.9199087 1.0000000
votes => rating 1.0000000 1.0000000 1.0000000 1.0000000
genre => rating
library(plotly)
# Calculer les proportions de chaque genre
genres <- colSums(mydata[, 5:28]) / nrow(mydata)
# Créer un vecteur de couleurs pour les secteurs
colors <- c("#1f77b4", "#ff7f0e", "#2ca02c")
# Créer un graphique camembert interactif avec plotly
plot_ly(
labels = names(genres),
values = genres,
type = "pie",
marker = list(colors = colors),
textposition = "inside",
textinfo = "percent+label",
hoverinfo = "label+percent",
title = "Genres des films"
)
mydata_3 <- mydata
# renommer les colonnes à partir de la 13ème colonne jusqu'à la dernière colonne
names(mydata_3)[5:28] <- paste0("genre_", names(mydata_3)[5:28])
on va s’interesser a trouver les type de films apprécié/non apprécié
par les gens,
(rating >= 8)
# Charger les packages nécessaires
library(plotly)
library(dplyr)
# Récupérer les films avec rating >= 8
rated_df <- filter(mydata_3, rating >= 8)
# Sélectionner les colonnes des genres
genre_cols <- names(rated_df)[5:28]
# Compter le nombre de films par genre
genre_counts <- colSums(rated_df[, genre_cols])
# Créer un data frame avec les résultats
genre_df <- data.frame(Genre = names(genre_counts),
Count = genre_counts)
# Créer le graphique à barres interactif avec Plotly
plot_ly(genre_df, x = ~Genre, y = ~Count, type = 'bar',
name = "Genres", marker = list(color = 'blue'),
xaxis = list(title = "Genres"),
yaxis = list(title = "Nombre de films"),
text = ~paste("Genre: ", Genre, "<br>", "Count: ", Count)) %>%
layout(title = "Distribution des genres pour les films avec un rating >= 8")
(rating <=4)
# Récupérer les films avec rating >= 8
rated_df <- filter(mydata_3, rating <= 4)
# Sélectionner les colonnes des genres
genre_cols <- names(rated_df)[5:28]
# Compter le nombre de films par genre
genre_counts <- colSums(rated_df[, genre_cols])
# Créer un data frame avec les résultats
genre_df <- data.frame(Genre = names(genre_counts),
Count = genre_counts)
# Créer le graphique à barres interactif avec Plotly
plot_ly(genre_df, x = ~Genre, y = ~Count, type = 'bar',
name = "Genres", marker = list(color = 'red'),
xaxis = list(title = "Genres"),
yaxis = list(title = "Nombre de films"),
text = ~paste("Genre: ", Genre, "<br>", "Count: ", Count)) %>%
layout(title = "Distribution des genres pour les films avec un rating <= 4")
on voit clairement que le genre Drama prend la premiere place avec
2330 films ayant plus que 8 de rating cela veut dire que le “genres” des
film peut avoir une influence sur le rating
EX: un film de gerne “DRAMA” a beaucoup plus de chance d’avoir un
rating élevé qu’un film de genre “NEWS”
aussi on peut voir que les films d’horreur des notes plus basses que
les films de comédie ou de drame. Cela peut être dû au fait que les
films d’horreur ont un public plus limité et peuvent être moins
populaires auprès de la majorité des utilisateurs d’IMDb
yea(r => rating
library(ggplot2)
# 1. Créer une nouvelle colonne pour l'année décennale
mydata_3$decade <- as.integer(floor(mydata_3$year/10) * 10)
ggplot(mydata_3, aes(x = decade, y = rating, color = rating)) +
geom_point() +
stat_smooth(method = "lm", se = FALSE)

on peut remarquer que les films plus anciens ont des notes plus
élevées que les films plus récents. Cela peut être dû à la nostalgie ou
au fait que les films plus anciens ont eu plus de temps pour devenir des
classiques et par la suite etre bien noté.
ce qui est interressant avec ce plot c’est qu’on peut voir que plus
on avance avec les années plus il y’a des film avec des rating entre [9
- 10] mais vu la moyenne (en baisse),on est certain que ca compense de
l’autre coté (mauvaises notes) donc on a aussi beaucoup de films ayant
des tres mauvaises notes
d’autre facteur peuve affecter le rating concernant le temps de
sortie des films,les saison par example (été/hiver) peuvent affecter les
stats des film, mais dans cette bdd on a pas malheureusement cette
information
runtime => rating
#Comportement des rating en fonction des runtime
mean_ratings <- aggregate(rating ~ runtime, mydata_3, mean)
ggplot(mean_ratings, aes(x=runtime, y=rating)) +
# ajouter une courbe
geom_line() +
# ajouter une étiquette pour l'axe x
xlab("Runtime") +
# ajouter une étiquette pour l'axe y
ylab("Rating moyen") +
# ajouter un titre
ggtitle("Comportement des ratings moyens en fonction des runtimes")

de cette courbe on peut voir que les meilleur rating sont atteint
avec des film d’une durée courte et que plus les films sont longs plus
leur rating baisse. Cela peut être dû au fait que les films plus longs
peuvent être moins engageants pour les utilisateurs, ou peuvent être
perçus comme étant trop longs ou ennuyeux
votes => rating
#Comportement des rating en fonction des runtime
mean_ratings <- aggregate(rating ~ votes, mydata_3, mean)
ggplot(mean_ratings, aes(x=votes, y=rating)) +
# ajouter une courbe
geom_line() +
# ajouter une étiquette pour l'axe x
xlab("nbr_votes") +
# ajouter une étiquette pour l'axe y
ylab("Rating moyen") +
# ajouter un titre
ggtitle("Comportement des ratings moyens en fonction des votes")

de ce graphic on peut clairement voir que Les films qui ont reçu un
grand nombre de votes ont des notes plus stables et plus fiables que les
films qui ont reçu un petit nombre de votes. Cela peut être dû au fait
que les films qui ont reçu un grand nombre de votes sont plus
représentatifs de l’opinion publique
certificate => rating
# Calculer les proportions de chaque genre
certificateS <- colSums(mydata[, 30:72]) / nrow(mydata)
# Créer un vecteur de couleurs pour les secteurs
colors <- c("#1f77b4", "#ff7f0e", "#2ca02c")
# Créer un graphique camembert interactif avec plotly
plot_ly(
labels = names(certificateS),
values = certificateS,
type = "pie",
marker = list(colors = colors),
textposition = "inside",
textinfo = "percent+label",
hoverinfo = "label+percent",
title = "certificatsdes films"
)
library(plotly)
library(dplyr)
mydata_3$`certificate_R` <- NULL
mydata_3$`certificate_Not Rated` <- NULL
# Récupérer les films avec rating >= 8
rated_df <- filter(mydata_3, rating >= 8)
# Sélectionner les colonnes des genres
certificate_cols <- names(rated_df)[30:70]
# Compter le nombre de films par certificate
certificate_counts <- colSums(rated_df[, certificate_cols])
# Créer un data frame avec les résultats
certificate_df <- data.frame(Certificate = names(certificate_counts),
Count = certificate_counts)
# Créer le graphique à barres interactif avec Plotly
plot_ly(certificate_df, x = ~Certificate, y = ~Count, type = 'bar',
name = "Certificates", marker = list(color = 'blue'),
xaxis = list(title = "Certificates"),
yaxis = list(title = "Nombre de films"),
text = ~paste("Certificate: ", Certificate, "<br>", "Count: ", Count)) %>%
layout(title = "Distribution des certificates pour les films avec un rating >= 8")
# Récupérer les films avec rating <=4
rated_df <- filter(mydata_3, rating <=4)
# Sélectionner les colonnes des genres
certificate_cols <- names(rated_df)[30:70]
# Compter le nombre de films par certificate
certificate_counts <- colSums(rated_df[, certificate_cols])
# Créer un data frame avec les résultats
certificate_df <- data.frame(Certificate = names(certificate_counts),
Count = certificate_counts)
# Créer le graphique à barres interactif avec Plotly
plot_ly(certificate_df, x = ~Certificate, y = ~Count, type = 'bar',
name = "Certificates", marker = list(color = 'red'),
xaxis = list(title = "Certificates"),
yaxis = list(title = "Nombre de films"),
text = ~paste("Certificate: ", Certificate, "<br>", "Count: ", Count)) %>%
layout(title = "Distribution des genres pour les films avec un rating <=4")
NA
comme on peut le voir clairement, il existe des certification qui
peuvent nous garantir le comportement du film (plutot son rating) comme
par example si un film est certifié “TV_MA” cela veut dire que le film
aura tres probablement un mauvais, car on la trouve beacoup ici dans les
films qui ont de mauvais rating et presque pas du tout chez les film qui
ont des bons ratings .
Training a regression model
mydata
#remove NA left in the dataframe
mydata <- na.omit(mydata)
#normaliser mes données
mydata <- scale(mydata)
#rendre mes données dataframe
mydata <- data.frame(mydata)
mydata
library('caret')
# Split the dataset into training and testing sets
set.seed(123)
trainIndex <- createDataPartition(mydata$rating, p = 0.8, list = FALSE)
trainData <- mydata[trainIndex, ]
testData <- mydata[-trainIndex, ]
# Train the linear regression model on the training set
lm_model <- train(rating ~ . , data = trainData, method = "lm")
Avis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeusesAvis : les prédictions venant d'un modèle de rang faible peuvent être trompeuses
# Make predictions on the test set using the trained model
predictions <- predict(lm_model, newdata = testData)
Avis : les prédictions venant d'un modèle de rang faible peuvent être trompeuses
# Calculate the root mean squared error (RMSE) to evaluate model performance
rmse <- sqrt(mean((testData$rating - predictions)^2))
# Print the RMSE value
print(paste0("RMSE: ", rmse))
[1] "RMSE: 0.897082980697212"
# Split the data into training and testing sets (70/30 split)
set.seed(123) # for reproducibility
train_index <- sample(1:nrow(mydata), size = round(0.7 * nrow(mydata)), replace = FALSE)
train_data <- mydata[train_index, ]
test_data <- mydata[-train_index, ]
# Fit a linear regression model to the training data to predict rating
model <- lm(rating ~ ., data = train_data)
# Predict the rating values for the testing data using the trained model
predictions <- predict(model, newdata = test_data)
Avis : les prédictions venant d'un modèle de rang faible peuvent être trompeuses
# Calculate the RMSE for the testing data
rmse <- sqrt(mean((predictions - test_data$rating)^2))
# Plot the learning process for the training data
learning_curve <- data.frame(
iteration = 1:length(model$residuals),
rmse = sqrt(cumsum(model$residuals^2)/length(model$residuals))
)
ggplot(learning_curve, aes(x = iteration, y = rmse)) +
geom_line(color = "blue") +
labs(title = "Learning curve", x = "Iteration", y = "RMSE")

# Print the RMSE for the testing data
print(paste("RMSE on testing data:", rmse))
[1] "RMSE on testing data: 0.898187143198805"
# Initialiser les vecteurs de stockage pour la performance d'entraînement et de validation
train_perf <- c()
val_perf <- c()
# Définir les tailles de sous-ensembles pour l'entraînement
train_sizes <- seq(2, nrow(train), by = 2)
# Tracer la courbe d'apprentissage
plot(train_sizes, train_perf, type = "l", col = "blue", xlab = "Taille de l'ensemble d'entraînement", ylab = "RMSE")
lines(train_sizes, val_perf, type = "l", col = "red")
legend("topright", legend = c("Entraînement", "test"), col = c("blue", "red"), lty = 1)

LS0tDQp0aXRsZTogIm1vdmllc19yYXRpbmdfc3R1ZHkiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCiNsZXMgcGFja2FnZXMgYSBpbnN0YWxsZXIgDQojaW5zdGFsbC5wYWNrYWdlcyhnZ3Bsb3QyKQ0KI2luc3RhbGwucGFja2FnZXMoZHBseXIpDQojaW5zdGFsbC5wYWNrYWdlcyh0aWR5cikNCiNpbnN0YWxsLnBhY2thZ2VzKGFydWxlcykNCiNpbnN0YWxsLnBhY2thZ2VzKGZhc3REdW1taWVzKQ0KI2luc3RhbGwucGFja2FnZXMocGxvdGx5KQ0KI2luc3RhbGwucGFja2FnZXMoc3RyaW5ncikNCmBgYA0KDQoNCmBgYHtyfQ0KDQojaW1wb3J0ZXIgZXQgZnVzaW9ubmVyIGxhIGJhc2UgZGUgZG9ubsOpZXMNCnNldHdkKCJDOi9Vc2Vycy91dGlsaXNhdGV1ci9EZXNrdG9wL2RhdGFfbWluaW5nX3Byb2plY3QiKQ0KZGlyZWN0b3J5IDwtICIuL2RhdGFzZXQiDQpmaWxlcyA8LSBsaXN0LmZpbGVzKGRpcmVjdG9yeSwgcGF0dGVybiA9ICIuY3N2IikNCmRhdGFfbGlzdCA8LSBsYXBwbHkoZmlsZXMsIGZ1bmN0aW9uKGZpbGUpIHJlYWQuY3N2KGZpbGUucGF0aChkaXJlY3RvcnksIGZpbGUpKSkNCm15ZGF0YSA8LSBkby5jYWxsKHJiaW5kLCBkYXRhX2xpc3QpDQoNCm15ZGF0YQ0KYGBgDQoNCg0KYGBge3J9DQojbmV0dG95YWdlIGRlIGxhIGJkZA0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZmFzdER1bW1pZXMpDQoNCiNkcm9wIGxlcyBhdHRyaWJ1dCBxdWkgdm9udCBwYXMgbm91cyBzZXJ2aXIgZGFucyBub3RyZSBldHVkZQ0KbXlkYXRhJGdyb3NzLmluLi4uIDwtIE5VTEwNCm15ZGF0YSRzdGFyX2lkIDwtIE5VTEwNCm15ZGF0YSRkaXJlY3Rvcl9pZCA8LSBOVUxMDQpteWRhdGEkZGVzY3JpcHRpb24gPC0gTlVMTA0KDQoNCiNvbiBzdXBwcmltZSBsZXMgZmlsbXMgcXVpIG4nb250IHBhcyByYXRpbmcNCm15ZGF0YSA8LSBteWRhdGFbY29tcGxldGUuY2FzZXMobXlkYXRhJHJhdGluZyksIF0NCg0KI29uIHN1cHByaW1lIGxlcyBmaWxtcyBxdWkgbidvbnQgcGFzIHJ1bnRpbWUNCm15ZGF0YSA8LSBteWRhdGFbY29tcGxldGUuY2FzZXMobXlkYXRhJHJ1bnRpbWUpLCBdDQoNCiNvbiBzdXBwcmltZSBsZXMgZmlsbXMgcXVpIG4nb250IHBhcyB2b3Rlcw0KbXlkYXRhIDwtIG15ZGF0YVtjb21wbGV0ZS5jYXNlcyhteWRhdGEkdm90ZXMpLCBdDQoNCiMgb24gc3VwcHJpbWUgbGVzIHZhbGV1cnMgbnVsbCBkYW5zIGxhIHRhYmxlIChOQSkNCm15ZGF0YSA8LSBuYS5vbWl0KG15ZGF0YSkNCg0KI29uIHN1cHByaW1lIGxlcyBkb3VibG9ucw0KbXlkYXRhIDwtIHVuaXF1ZShteWRhdGEsIGJ5ID0gIm1vdmllX2lkIikNCg0KIyBlbmxldmVyIGxlIG1vdCAnbWluJyBkZSAicnVudGltZSIgZXQgY2hhbmdlciBzb24gdHlwZQ0KbXlkYXRhJHJ1bnRpbWUgPC0gZ3N1YigibWluIiwgIiIsIG15ZGF0YSRydW50aW1lKQ0KDQojIENvbnZlcnRpciBsYSBjb2xvbm5lICJydW50aW1lIiBlbiB0eXBlIG51bcOpcmlxdWUNCm15ZGF0YSRydW50aW1lIDwtIGFzLm51bWVyaWMobXlkYXRhJHJ1bnRpbWUpDQoNCiMgQ29udmVydGlyIGxhIGNvbG9ubmUgInllYXIiIGVuIHR5cGUgbnVtw6lyaXF1ZQ0KbXlkYXRhJHllYXIgPC0gYXMubnVtZXJpYyhteWRhdGEkeWVhcikNCg0KY29sU3Vtcyhpcy5uYShteWRhdGEpKQ0KDQojIHV0aWxpc2VyIHNlcGFyYXRlKCkgcG91ciBjcsOpZXIgdW5lIGNvbG9ubmUgcG91ciBjaGFxdWUgdmFsZXVyIGRhbnMgImdlbnJlcyINCm15ZGF0YSA8LSBteWRhdGEgJT4lIHNlcGFyYXRlX3Jvd3MoZ2VucmUsIHNlcCA9ICIsICIpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gZ2VucmUsIHZhbHVlc19mcm9tID0gZ2VucmUsIHZhbHVlc19mbiA9IGxlbmd0aCwgdmFsdWVzX2ZpbGwgPSAwKQ0KDQoNCm15ZGF0YQ0KYGBgDQoNCmBgYHtyfQ0KIyBvbiBzZXBhcmUgbGVzIGRpZmZlcmVudCBjZXJ0aWZpY2F0IChjaGFxdWUgdW5lIHN1ciB1bmUgY29sb25uZSkNCm15ZGF0YSA8LSBkdW1teV9jb2xzKG15ZGF0YSwgc2VsZWN0X2NvbHVtbnMgPSAiY2VydGlmaWNhdGUiKQ0KI29uIGRyb3AgY2VydGlmaWNhdGUgZXQgc3Rhcg0KbXlkYXRhJGNlcnRpZmljYXRlIDwtIE5VTEwNCm15ZGF0YSRzdGFyIDwtIE5VTEwNCm15ZGF0YQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCiMgQ29tcHRlciBsZSBub21icmUgZGUgZmlsbXMgcGFyIHJhdGluZw0KZmlsbV9wZXJfcmF0aW5nIDwtIG15ZGF0YSAlPiUgDQogIGdyb3VwX2J5KHJhdGluZykgJT4lIA0KICBzdW1tYXJpemUobiA9IG4oKSkNCg0KIyBUcmFjZXIgdW4gaGlzdG9ncmFtbWUgZHUgcmF0aW5nIGRlcyBmaWxtcw0KZ2dwbG90KGRhdGEgPSBmaWxtX3Blcl9yYXRpbmcsIGFlcyh4ID0gcmF0aW5nLCB5ID0gbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGNvbG9yID0gInJlZCIsIGZpbGwgPSAiYmx1ZSIsIGFscGhhID0gMC41KSArDQogIGxhYnModGl0bGUgPSAiSGlzdG9ncmFtbWUgZHUgcmF0aW5nIGRlcyBmaWxtcyIsIHggPSAiUmF0aW5nIiwgeSA9ICJOb21icmUgZGUgZmlsbXMiKQ0KYGBgDQpvbiByZW1hcnFlIHF1ZSBub3MgZG9ubsOpZXMgb250IHVuZSBkaXN0cmlidXRpb24gbm9ybWFsZSBjZW50csOpZSBzdXIgbGEgdmFsZXVyIDYsDQpjZWxhIHZldXQgZGlyZSBxdSdpbCB5J2EgcGx1cyBkZSBwcm9iYWxpYml0w6kgcG91ciB1biBmdXR1ciBmaWxtIHF1J2lsIHNvaXQgbm90w6kgNi8xMCBxdWUgYXV0cmUgY2hvc2UuDQoNCmRlIGxhIG9uIHBldSBjb25zaWTDqXJlciBxdSd1biBmaWxtIHF1aSBhIHVuIChSQVRJTkcgPj04KSBlc3QgdW4gZmlsbSByZXVzc2kgZXQgKFJBVElORyA8PTQpIGVzdCB1biBlY2hlYw0KDQpgYGB7cn0NCg0KI29uIHByZW5kcyB1bmUgY29waWUgZGUgbGEgZGF0YWZyYW1lIChwb3VyIGFuYWx5c2VyIGxlcyBhcnRpc3RzKQ0KDQpteWRhdGEkbW92aWVfbmFtZSA8LSBOVUxMDQpteWRhdGEkbW92aWVfaWQgPC0gTlVMTA0KbXlkYXRhJGRpcmVjdG9yIDwtIE5VTEwNCg0KIyBpbnN0YWxsZXIgZXQgY2hhcmdlciBsYSBsaWJyYWlyaWUgYXJ1bGVzDQpsaWJyYXJ5KGFydWxlcykNCg0KIyBjcsOpZXIgdW5lIG1hdHJpY2UgYmluYWlyZSBkZSB0cmFuc2FjdGlvbnMgw6AgcGFydGlyIGRlIGxhIGRhdGFmcmFtZQ0KdHJhbnNhY3Rpb25zX21hdCA8LSBhcy5tYXRyaXgobXlkYXRhKQ0KDQojIHJlbXBsYWNlciBsZXMgdmFsZXVycyBtYW5xdWFudGVzIHBhciAwDQp0cmFuc2FjdGlvbnNfbWF0W2lzLm5hKHRyYW5zYWN0aW9uc19tYXQpXSA8LSAwDQp0cmFuc2FjdGlvbnNfbWF0W3RyYW5zYWN0aW9uc19tYXQgPiAwXSA8LSAxDQoNCiMgY29udmVydGlyIGxhIG1hdHJpY2UgZGUgdHJhbnNhY3Rpb25zIGVuIHVuIG9iamV0ICJ0cmFuc2FjdGlvbnMiIGRlIGxhIGxpYnJhaXJpZSBhcnVsZXMNCnRyYW5zYWN0aW9uc19vYmogPC0gYXModHJhbnNhY3Rpb25zX21hdCwgInRyYW5zYWN0aW9ucyIpDQoNCiMgYXBwbGlxdWVyIGxhIGZvbmN0aW9uIGFwcmlvcmkgcG91ciBleHRyYWlyZSBsZXMgcsOoZ2xlcyBkJ2Fzc29jaWF0aW9uDQpydWxlcyA8LSBhcHJpb3JpKHRyYW5zYWN0aW9uc19vYmosIHBhcmFtZXRlciA9IGxpc3Qoc3VwcG9ydCA9IDAuMSwgY29uZmlkZW5jZSA9IDAuNSwgbWlubGVuID0gMikpDQoNCiMgYWZmaWNoZXIgbGVzIHLDqGdsZXMgZCdhc3NvY2lhdGlvbg0KaW5zcGVjdChydWxlcykNCmBgYA0KDQpkJ2FwcmVzIGxlcyByZXN1bHRhdCBkZSBsJ2FsZ29yaXRobWUgYXByaW9yaSBvbiB2b2l0IHF1J2lsIHknYSB1bmUgcmVsYXRpb24gZm9ydGUgZW50cmUge3JhdGluZ30gZXQge3llYXIvcnVudGltZS92b3Rlc30gYWluc2kgcXVlIGxlcyBkaWZmZXJlbnRzIGdlbnJlIHF1J29uIGRhbnMgbGEgZGF0YWZyYW1lDQpjZWxhIGVzdCB2dSBncmFjZSBhdSBtZXN1cmUgKHN1cHBvcnQvY29uZmlhbmNlL2xpZnQpIChwbHVzIGxldXIgdmFsZXVycyBlc3Qgw6lsZXbDqSBwbHVzIGxhIHJlbGF0aW9uIGVzdCBmb3J0ZSkNCg0KLUxlIHN1cHBvcnQgKG91IHNvdXRpZW4pIGVzdCB1bmUgbWVzdXJlIGRlIGxhIGZyw6lxdWVuY2UgZCdhcHBhcml0aW9uIGQndW4gaXRlbXNldCBkYW5zIHVuIGVuc2VtYmxlIGRlIHRyYW5zYWN0aW9ucw0KDQotTGEgY29uZmlhbmNlIGVzdCB1bmUgbWVzdXJlIGRlIGxhIHByb2JhYmlsaXTDqSBjb25kaXRpb25uZWxsZSBkJ29ic2VydmVyIGxlIGNvbnPDqXF1ZW50IGQndW5lIHLDqGdsZSBzYWNoYW50IHF1ZSBsJ2FudMOpY8OpZGVudCBlc3QgcHLDqXNlbnQNCg0KLUxlIGxpZnQgZXN0IHVuZSBtZXN1cmUgZGUgbCdhc3NvY2lhdGlvbiBlbnRyZSBsJ2FudMOpY8OpZGVudCBldCBsZSBjb25zw6lxdWVudCBkJ3VuZSByw6hnbGUNCg0KICAgICAgICAgICAgICAgICAgICBzdXBwb3J0ICAgIGNvbmZpZGVuY2UgICAgY292ZXJhZ2UgICAgbGlmdCAgDQp5ZWFyID0+IHJhdGluZyAgICAgMC45OTk4Njc5CSAxLjAwMDAwMDAJICAwLjk5OTg2NzkJIDEuMDAwMDAwMAkNCnJ1bnRpbWUgPT4gcmF0aW5nICAwLjkxOTkwODcgICAxLjAwMDAwMDAgICAgMC45MTk5MDg3CSAxLjAwMDAwMDAJDQp2b3Rlcwk9PglyYXRpbmcJIDEuMDAwMDAwMAkgMS4wMDAwMDAwCSAgMS4wMDAwMDAwCSAxLjAwMDAwMDANCg0KDQoNCjxoMT4gZ2VucmUgPT4gcmF0aW5nIDwvaDE+DQoNCmBgYHtyfQ0KbGlicmFyeShwbG90bHkpDQoNCiMgQ2FsY3VsZXIgbGVzIHByb3BvcnRpb25zIGRlIGNoYXF1ZSBnZW5yZQ0KZ2VucmVzIDwtIGNvbFN1bXMobXlkYXRhWywgNToyOF0pIC8gbnJvdyhteWRhdGEpDQoNCiMgQ3LDqWVyIHVuIHZlY3RldXIgZGUgY291bGV1cnMgcG91ciBsZXMgc2VjdGV1cnMNCmNvbG9ycyA8LSBjKCIjMWY3N2I0IiwgIiNmZjdmMGUiLCAiIzJjYTAyYyIpDQoNCiMgQ3LDqWVyIHVuIGdyYXBoaXF1ZSBjYW1lbWJlcnQgaW50ZXJhY3RpZiBhdmVjIHBsb3RseQ0KcGxvdF9seSgNCiAgbGFiZWxzID0gbmFtZXMoZ2VucmVzKSwNCiAgdmFsdWVzID0gZ2VucmVzLA0KICB0eXBlID0gInBpZSIsDQogIG1hcmtlciA9IGxpc3QoY29sb3JzID0gY29sb3JzKSwNCiAgdGV4dHBvc2l0aW9uID0gImluc2lkZSIsDQogIA0KICANCiAgdGV4dGluZm8gPSAicGVyY2VudCtsYWJlbCIsDQogIGhvdmVyaW5mbyA9ICJsYWJlbCtwZXJjZW50IiwNCiAgdGl0bGUgPSAiR2VucmVzIGRlcyBmaWxtcyINCikNCmBgYA0KDQoNCmBgYHtyfQ0KbXlkYXRhXzMgPC0gbXlkYXRhDQoNCiMgcmVub21tZXIgbGVzIGNvbG9ubmVzIMOgIHBhcnRpciBkZSBsYSAxM8OobWUgY29sb25uZSBqdXNxdSfDoCBsYSBkZXJuacOocmUgY29sb25uZQ0KbmFtZXMobXlkYXRhXzMpWzU6MjhdIDwtIHBhc3RlMCgiZ2VucmVfIiwgbmFtZXMobXlkYXRhXzMpWzU6MjhdKQ0KYGBgDQoNCm9uIHZhIHMnaW50ZXJlc3NlciBhIHRyb3V2ZXIgbGVzIHR5cGUgZGUgZmlsbXMgYXBwcsOpY2nDqS9ub24gYXBwcsOpY2nDqSBwYXIgbGVzIGdlbnMsDQoNCihyYXRpbmcgPj0gOCkNCmBgYHtyfQ0KIyBSw6ljdXDDqXJlciBsZXMgZmlsbXMgYXZlYyByYXRpbmcgPj0gOA0KcmF0ZWRfZGYgPC0gZmlsdGVyKG15ZGF0YV8zLCByYXRpbmcgPj0gOCkNCg0KIyBTw6lsZWN0aW9ubmVyIGxlcyBjb2xvbm5lcyBkZXMgZ2VucmVzDQpnZW5yZV9jb2xzIDwtIG5hbWVzKHJhdGVkX2RmKVs1OjI4XQ0KDQojIENvbXB0ZXIgbGUgbm9tYnJlIGRlIGZpbG1zIHBhciBnZW5yZQ0KZ2VucmVfY291bnRzIDwtIGNvbFN1bXMocmF0ZWRfZGZbLCBnZW5yZV9jb2xzXSkNCg0KIyBDcsOpZXIgdW4gZGF0YSBmcmFtZSBhdmVjIGxlcyByw6lzdWx0YXRzDQpnZW5yZV9kZiA8LSBkYXRhLmZyYW1lKEdlbnJlID0gbmFtZXMoZ2VucmVfY291bnRzKSwgDQogICAgICAgICAgICAgICAgICAgICAgIENvdW50ID0gZ2VucmVfY291bnRzKQ0KDQojIENyw6llciBsZSBncmFwaGlxdWUgw6AgYmFycmVzIGludGVyYWN0aWYgYXZlYyBQbG90bHkNCnBsb3RfbHkoZ2VucmVfZGYsIHggPSB+R2VucmUsIHkgPSB+Q291bnQsIHR5cGUgPSAnYmFyJywgDQogICAgICAgIG5hbWUgPSAiR2VucmVzIiwgbWFya2VyID0gbGlzdChjb2xvciA9ICdibHVlJyksDQogICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJHZW5yZXMiKSwgDQogICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJOb21icmUgZGUgZmlsbXMiKSwNCiAgICAgICAgdGV4dCA9IH5wYXN0ZSgiR2VucmU6ICIsIEdlbnJlLCAiPGJyPiIsICJDb3VudDogIiwgQ291bnQpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkZXMgZ2VucmVzIHBvdXIgbGVzIGZpbG1zIGF2ZWMgdW4gcmF0aW5nID49IDgiKQ0KYGBgDQoNCihyYXRpbmcgPD00KQ0KDQpgYGB7cn0NCiMgUsOpY3Vww6lyZXIgbGVzIGZpbG1zIGF2ZWMgcmF0aW5nID49IDgNCnJhdGVkX2RmIDwtIGZpbHRlcihteWRhdGFfMywgcmF0aW5nIDw9IDQpDQoNCiMgU8OpbGVjdGlvbm5lciBsZXMgY29sb25uZXMgZGVzIGdlbnJlcw0KZ2VucmVfY29scyA8LSBuYW1lcyhyYXRlZF9kZilbNToyOF0NCg0KIyBDb21wdGVyIGxlIG5vbWJyZSBkZSBmaWxtcyBwYXIgZ2VucmUNCmdlbnJlX2NvdW50cyA8LSBjb2xTdW1zKHJhdGVkX2RmWywgZ2VucmVfY29sc10pDQoNCiMgQ3LDqWVyIHVuIGRhdGEgZnJhbWUgYXZlYyBsZXMgcsOpc3VsdGF0cw0KZ2VucmVfZGYgPC0gZGF0YS5mcmFtZShHZW5yZSA9IG5hbWVzKGdlbnJlX2NvdW50cyksIA0KICAgICAgICAgICAgICAgICAgICAgICBDb3VudCA9IGdlbnJlX2NvdW50cykNCg0KIyBDcsOpZXIgbGUgZ3JhcGhpcXVlIMOgIGJhcnJlcyBpbnRlcmFjdGlmIGF2ZWMgUGxvdGx5DQpwbG90X2x5KGdlbnJlX2RmLCB4ID0gfkdlbnJlLCB5ID0gfkNvdW50LCB0eXBlID0gJ2JhcicsIA0KICAgICAgICBuYW1lID0gIkdlbnJlcyIsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmVkJyksDQogICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJHZW5yZXMiKSwgDQogICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJOb21icmUgZGUgZmlsbXMiKSwNCiAgICAgICAgdGV4dCA9IH5wYXN0ZSgiR2VucmU6ICIsIEdlbnJlLCAiPGJyPiIsICJDb3VudDogIiwgQ291bnQpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkZXMgZ2VucmVzIHBvdXIgbGVzIGZpbG1zIGF2ZWMgdW4gcmF0aW5nIDw9IDQiKQ0KYGBgDQoNCm9uIHZvaXQgY2xhaXJlbWVudCBxdWUgbGUgZ2VucmUgRHJhbWEgcHJlbmQgbGEgcHJlbWllcmUgcGxhY2UgYXZlYyAyMzMwIGZpbG1zIGF5YW50IHBsdXMgcXVlIDggZGUgcmF0aW5nIA0KY2VsYSB2ZXV0IGRpcmUgcXVlIGxlICJnZW5yZXMiIGRlcyBmaWxtIHBldXQgYXZvaXIgdW5lIGluZmx1ZW5jZSBzdXIgbGUgcmF0aW5nIA0KDQpFWDogdW4gZmlsbSBkZSBnZXJuZSAiRFJBTUEiIGEgYmVhdWNvdXAgcGx1cyBkZSBjaGFuY2UgZCdhdm9pciB1biByYXRpbmcgw6lsZXbDqSBxdSd1biBmaWxtIGRlIGdlbnJlICJORVdTIg0KDQphdXNzaSBvbiBwZXV0IHZvaXIgcXVlIGxlcyBmaWxtcyBkJ2hvcnJldXIgZGVzIG5vdGVzIHBsdXMgYmFzc2VzIHF1ZSBsZXMgZmlsbXMgZGUgY29tw6lkaWUgb3UgZGUgZHJhbWUuIENlbGEgcGV1dCDDqnRyZSBkw7sgYXUgZmFpdCBxdWUgbGVzIGZpbG1zIGQnaG9ycmV1ciBvbnQgdW4gcHVibGljIHBsdXMgbGltaXTDqSBldCBwZXV2ZW50IMOqdHJlIG1vaW5zIHBvcHVsYWlyZXMgYXVwcsOocyBkZSBsYSBtYWpvcml0w6kgZGVzIHV0aWxpc2F0ZXVycyBkJ0lNRGINCg0KPGgxPiB5ZWEociA9PiByYXRpbmcgPC9oMT4NCg0KYGBge3J9DQojQ3LDqWVyIHVuZSBub3V2ZWxsZSBjb2xvbm5lIHBvdXIgbCdhbm7DqWUgZMOpY2VubmFsZQ0KbXlkYXRhXzMkZGVjYWRlIDwtIGFzLmludGVnZXIoZmxvb3IobXlkYXRhXzMkeWVhci8xMCkgKiAxMCkNCg0KZ2dwbG90KG15ZGF0YV8zLCBhZXMoeCA9IGRlY2FkZSwgeSA9IHJhdGluZywgY29sb3IgPSByYXRpbmcpKSArDQogIGdlb21fcG9pbnQoKSArDQogIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpDQpgYGANCg0Kb24gcGV1dCByZW1hcnF1ZXIgcXVlIGxlcyBmaWxtcyBwbHVzIGFuY2llbnMgb250IGRlcyBub3RlcyBwbHVzIMOpbGV2w6llcyBxdWUgbGVzIGZpbG1zIHBsdXMgcsOpY2VudHMuIENlbGEgcGV1dCDDqnRyZSBkw7sgw6AgbGEgbm9zdGFsZ2llIG91IGF1IGZhaXQgcXVlIGxlcyBmaWxtcyBwbHVzIGFuY2llbnMgb250IGV1IHBsdXMgZGUgdGVtcHMgcG91ciBkZXZlbmlyIGRlcyBjbGFzc2lxdWVzIGV0IHBhciBsYSBzdWl0ZSBldHJlIGJpZW4gbm90w6kuDQoNCmNlIHF1aSBlc3QgaW50ZXJyZXNzYW50IGF2ZWMgY2UgcGxvdCBjJ2VzdCBxdSdvbiBwZXV0IHZvaXIgcXVlIHBsdXMgb24gYXZhbmNlIGF2ZWMgbGVzIGFubsOpZXMgcGx1cyBpbCB5J2EgZGVzIGZpbG0gYXZlYyBkZXMgcmF0aW5nIGVudHJlIFs5IC0gMTBdIA0KbWFpcyB2dSBsYSBtb3llbm5lIChlbiBiYWlzc2UpLG9uIGVzdCBjZXJ0YWluIHF1ZSBjYSBjb21wZW5zZSBkZSBsJ2F1dHJlIGNvdMOpIChtYXV2YWlzZXMgbm90ZXMpIGRvbmMgb24gYSBhdXNzaSBiZWF1Y291cCBkZSBmaWxtcyBheWFudCBkZXMgdHJlcyBtYXV2YWlzZXMgbm90ZXMNCg0KZCdhdXRyZSBmYWN0ZXVyIHBldXZlIGFmZmVjdGVyIGxlIHJhdGluZyBjb25jZXJuYW50IGxlIHRlbXBzIGRlIHNvcnRpZSBkZXMgZmlsbXMsbGVzIHNhaXNvbiBwYXIgZXhhbXBsZSAow6l0w6kvaGl2ZXIpIHBldXZlbnQgYWZmZWN0ZXIgbGVzIHN0YXRzIGRlcyBmaWxtLCBtYWlzIGRhbnMgY2V0dGUgYmRkIG9uIGEgcGFzIG1hbGhldXJldXNlbWVudCBjZXR0ZSBpbmZvcm1hdGlvbiANCg0KDQo8aDE+IHJ1bnRpbWUgPT4gcmF0aW5nIDwvaDE+DQoNCmBgYHtyfQ0KI0NvbXBvcnRlbWVudCBkZXMgcmF0aW5nIGVuIGZvbmN0aW9uIGRlcyBydW50aW1lDQptZWFuX3JhdGluZ3MgPC0gYWdncmVnYXRlKHJhdGluZyB+IHJ1bnRpbWUsIG15ZGF0YV8zLCBtZWFuKQ0KDQpnZ3Bsb3QobWVhbl9yYXRpbmdzLCBhZXMoeD1ydW50aW1lLCB5PXJhdGluZykpICsNCiAgIyBham91dGVyIHVuZSBjb3VyYmUNCiAgZ2VvbV9saW5lKCkgKw0KICAjIGFqb3V0ZXIgdW5lIMOpdGlxdWV0dGUgcG91ciBsJ2F4ZSB4DQogIHhsYWIoIlJ1bnRpbWUiKSArDQogICMgYWpvdXRlciB1bmUgw6l0aXF1ZXR0ZSBwb3VyIGwnYXhlIHkNCiAgeWxhYigiUmF0aW5nIG1veWVuIikgKw0KICAjIGFqb3V0ZXIgdW4gdGl0cmUNCiAgZ2d0aXRsZSgiQ29tcG9ydGVtZW50IGRlcyByYXRpbmdzIG1veWVucyBlbiBmb25jdGlvbiBkZXMgcnVudGltZXMiKQ0KYGBgDQpkZSBjZXR0ZSBjb3VyYmUgb24gcGV1dCB2b2lyIHF1ZSBsZXMgbWVpbGxldXIgcmF0aW5nIHNvbnQgYXR0ZWludCBhdmVjIGRlcyBmaWxtIGQndW5lIGR1csOpZSBjb3VydGUgDQpldCBxdWUgcGx1cyBsZXMgZmlsbXMgc29udCBsb25ncyBwbHVzIGxldXIgcmF0aW5nIGJhaXNzZS4gQ2VsYSBwZXV0IMOqdHJlIGTDuyBhdSBmYWl0IHF1ZSBsZXMgZmlsbXMgcGx1cyBsb25ncyBwZXV2ZW50IMOqdHJlIG1vaW5zIGVuZ2FnZWFudHMgcG91ciBsZXMgdXRpbGlzYXRldXJzLCBvdSBwZXV2ZW50IMOqdHJlIHBlcsOndXMgY29tbWUgw6l0YW50IHRyb3AgbG9uZ3Mgb3UgZW5udXlldXgNCg0KDQo8aDE+IHZvdGVzID0+IHJhdGluZyA8L2gxPg0KYGBge3J9DQoNCiNDb21wb3J0ZW1lbnQgZGVzIHJhdGluZyBlbiBmb25jdGlvbiBkZXMgcnVudGltZQ0KbWVhbl9yYXRpbmdzIDwtIGFnZ3JlZ2F0ZShyYXRpbmcgfiB2b3RlcywgbXlkYXRhXzMsIG1lYW4pDQoNCmdncGxvdChtZWFuX3JhdGluZ3MsIGFlcyh4PXZvdGVzLCB5PXJhdGluZykpICsNCiAgZ2VvbV9saW5lKCkgKw0KICB4bGFiKCJuYnJfdm90ZXMiKSArDQogIHlsYWIoIlJhdGluZyBtb3llbiIpICsNCiAgZ2d0aXRsZSgiQ29tcG9ydGVtZW50IGRlcyByYXRpbmdzIG1veWVucyBlbiBmb25jdGlvbiBkZXMgdm90ZXMiKQ0KYGBgDQpkZSBjZSBncmFwaGljIG9uIHBldXQgY2xhaXJlbWVudCB2b2lyIHF1ZSBMZXMgZmlsbXMgcXVpIG9udCByZcOndSB1biBncmFuZCBub21icmUgZGUgdm90ZXMgb250IGRlcyBub3RlcyBwbHVzIHN0YWJsZXMgZXQgcGx1cyBmaWFibGVzIHF1ZSBsZXMgZmlsbXMgcXVpIG9udCByZcOndSB1biBwZXRpdCBub21icmUgZGUgdm90ZXMuIENlbGEgcGV1dCDDqnRyZSBkw7sgYXUgZmFpdCBxdWUgbGVzIGZpbG1zIHF1aSBvbnQgcmXDp3UgdW4gZ3JhbmQgbm9tYnJlIGRlIHZvdGVzIHNvbnQgcGx1cyByZXByw6lzZW50YXRpZnMgZGUgbCdvcGluaW9uIHB1YmxpcXVlDQoNCg0KPGgxPiBjZXJ0aWZpY2F0ZSA9PiByYXRpbmcgPC9oMT4NCg0KDQpgYGB7cn0NCg0KDQojIENhbGN1bGVyIGxlcyBwcm9wb3J0aW9ucyBkZSBjaGFxdWUgZ2VucmUNCmNlcnRpZmljYXRlUyA8LSBjb2xTdW1zKG15ZGF0YVssIDMwOjcyXSkgLyBucm93KG15ZGF0YSkNCg0KIyBDcsOpZXIgdW4gdmVjdGV1ciBkZSBjb3VsZXVycyBjb21tZSBwYWxsZXR0ZQ0KY29sb3JzIDwtIGMoIiMxZjc3YjQiLCAiI2ZmN2YwZSIsICIjMmNhMDJjIikNCg0KIyBDcsOpZXIgdW4gZ3JhcGhpcXVlIGNhbWVtYmVydCBpbnRlcmFjdGlmIGF2ZWMgcGxvdGx5DQpwbG90X2x5KA0KICBsYWJlbHMgPSBuYW1lcyhjZXJ0aWZpY2F0ZVMpLA0KICB2YWx1ZXMgPSBjZXJ0aWZpY2F0ZVMsDQogIHR5cGUgPSAicGllIiwNCiAgbWFya2VyID0gbGlzdChjb2xvcnMgPSBjb2xvcnMpLA0KICB0ZXh0cG9zaXRpb24gPSAiaW5zaWRlIiwNCiAgDQogIA0KICB0ZXh0aW5mbyA9ICJwZXJjZW50K2xhYmVsIiwNCiAgaG92ZXJpbmZvID0gImxhYmVsK3BlcmNlbnQiLA0KICB0aXRsZSA9ICJjZXJ0aWZpY2F0c2RlcyBmaWxtcyINCikNCmBgYA0KDQpgYGB7cn0NCiNvbiBkcm9wICJjZXJ0aWZpY2F0ZV9SIiBldCAiY2VydGlmaWNhdGVfTm90IFJhdGVkIiBwb3VyIHZvaXIgcGx1cyBjbGFpcg0KbXlkYXRhXzMkYGNlcnRpZmljYXRlX1JgIDwtIE5VTEwNCm15ZGF0YV8zJGBjZXJ0aWZpY2F0ZV9Ob3QgUmF0ZWRgIDwtIE5VTEwNCmBgYA0KDQpgYGB7cn0NCiMgUsOpY3Vww6lyZXIgbGVzIGZpbG1zIGF2ZWMgcmF0aW5nID49IDgNCnJhdGVkX2RmIDwtIGZpbHRlcihteWRhdGFfMywgcmF0aW5nID49IDgpDQoNCiMgU8OpbGVjdGlvbm5lciBsZXMgY29sb25uZXMgZGVzIGNlcnRpZmljYXRlDQpjZXJ0aWZpY2F0ZV9jb2xzIDwtIG5hbWVzKHJhdGVkX2RmKVszMDo3MF0NCg0KIyBDb21wdGVyIGxlIG5vbWJyZSBkZSBmaWxtcyBwYXIgY2VydGlmaWNhdGUNCmNlcnRpZmljYXRlX2NvdW50cyA8LSBjb2xTdW1zKHJhdGVkX2RmWywgY2VydGlmaWNhdGVfY29sc10pDQoNCiMgQ3LDqWVyIHVuIGRhdGEgZnJhbWUgYXZlYyBsZXMgcsOpc3VsdGF0cw0KY2VydGlmaWNhdGVfZGYgPC0gZGF0YS5mcmFtZShDZXJ0aWZpY2F0ZSA9IG5hbWVzKGNlcnRpZmljYXRlX2NvdW50cyksIA0KICAgICAgICAgICAgICAgICAgICAgICBDb3VudCA9IGNlcnRpZmljYXRlX2NvdW50cykNCg0KIyBDcsOpZXIgbGUgZ3JhcGhpcXVlIMOgIGJhcnJlcyBpbnRlcmFjdGlmIGF2ZWMgUGxvdGx5DQpwbG90X2x5KGNlcnRpZmljYXRlX2RmLCB4ID0gfkNlcnRpZmljYXRlLCB5ID0gfkNvdW50LCB0eXBlID0gJ2JhcicsIA0KICAgICAgICBuYW1lID0gIkNlcnRpZmljYXRlcyIsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAnYmx1ZScpLA0KICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiQ2VydGlmaWNhdGVzIiksIA0KICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiTm9tYnJlIGRlIGZpbG1zIiksDQogICAgICAgIHRleHQgPSB+cGFzdGUoIkNlcnRpZmljYXRlOiAiLCBDZXJ0aWZpY2F0ZSwgIjxicj4iLCAiQ291bnQ6ICIsIENvdW50KSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICJEaXN0cmlidXRpb24gZGVzIGNlcnRpZmljYXRlcyBwb3VyIGxlcyBmaWxtcyBhdmVjIHVuIHJhdGluZyA+PSA4IikNCmBgYA0KYGBge3J9DQojIFLDqWN1cMOpcmVyIGxlcyBmaWxtcyBhdmVjIHJhdGluZyA8PTQNCnJhdGVkX2RmIDwtIGZpbHRlcihteWRhdGFfMywgcmF0aW5nIDw9NCkNCg0KIyBTw6lsZWN0aW9ubmVyIGxlcyBjb2xvbm5lcyBkZXMgZ2VucmVzDQpjZXJ0aWZpY2F0ZV9jb2xzIDwtIG5hbWVzKHJhdGVkX2RmKVszMDo3MF0NCg0KIyBDb21wdGVyIGxlIG5vbWJyZSBkZSBmaWxtcyBwYXIgY2VydGlmaWNhdGUNCmNlcnRpZmljYXRlX2NvdW50cyA8LSBjb2xTdW1zKHJhdGVkX2RmWywgY2VydGlmaWNhdGVfY29sc10pDQoNCiMgQ3LDqWVyIHVuIGRhdGEgZnJhbWUgYXZlYyBsZXMgcsOpc3VsdGF0cw0KY2VydGlmaWNhdGVfZGYgPC0gZGF0YS5mcmFtZShDZXJ0aWZpY2F0ZSA9IG5hbWVzKGNlcnRpZmljYXRlX2NvdW50cyksIA0KICAgICAgICAgICAgICAgICAgICAgICBDb3VudCA9IGNlcnRpZmljYXRlX2NvdW50cykNCg0KIyBDcsOpZXIgbGUgZ3JhcGhpcXVlIMOgIGJhcnJlcyBpbnRlcmFjdGlmIGF2ZWMgUGxvdGx5DQpwbG90X2x5KGNlcnRpZmljYXRlX2RmLCB4ID0gfkNlcnRpZmljYXRlLCB5ID0gfkNvdW50LCB0eXBlID0gJ2JhcicsIA0KICAgICAgICBuYW1lID0gIkNlcnRpZmljYXRlcyIsIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmVkJyksDQogICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJDZXJ0aWZpY2F0ZXMiKSwgDQogICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJOb21icmUgZGUgZmlsbXMiKSwNCiAgICAgICAgdGV4dCA9IH5wYXN0ZSgiQ2VydGlmaWNhdGU6ICIsIENlcnRpZmljYXRlLCAiPGJyPiIsICJDb3VudDogIiwgQ291bnQpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkZXMgZ2VucmVzIHBvdXIgbGVzIGZpbG1zIGF2ZWMgdW4gcmF0aW5nIDw9NCIpDQoNCmBgYA0KY29tbWUgb24gcGV1dCBsZSB2b2lyIGNsYWlyZW1lbnQsIGlsIGV4aXN0ZSBkZXMgY2VydGlmaWNhdGlvbiBxdWkgcGV1dmVudCBub3VzIGdhcmFudGlyIGxlIGNvbXBvcnRlbWVudCBkdSBmaWxtIChwbHV0b3Qgc29uIHJhdGluZykgY29tbWUgcGFyIGV4YW1wbGUgc2kgdW4gZmlsbSBlc3QgY2VydGlmacOpICJUVl9NQSIgY2VsYSB2ZXV0IGRpcmUgcXVlIGxlIGZpbG0gYXVyYSB0cmVzIHByb2JhYmxlbWVudCB1biBtYXV2YWlzLCBjYXIgb24gbGEgdHJvdXZlIGJlYWNvdXAgaWNpIGRhbnMgbGVzIGZpbG1zIHF1aSBvbnQgZGUgbWF1dmFpcyByYXRpbmcgZXQgcHJlc3F1ZSBwYXMgZHUgdG91dCBjaGV6IGxlcyBmaWxtIHF1aSBvbnQgZGVzIGJvbnMgcmF0aW5ncyAuDQoNCjxoMT5UcmFpbmluZyBhIHJlZ3Jlc3Npb24gbW9kZWw8L2gxPg0KDQpgYGB7cn0NCm15ZGF0YQ0KYGBgDQpgYGB7cn0NCiNyZW1vdmUgTkEgbGVmdCBpbiB0aGUgZGF0YWZyYW1lDQpteWRhdGEgPC0gbmEub21pdChteWRhdGEpDQojbm9ybWFsaXNlciBtZXMgZG9ubsOpZXMNCm15ZGF0YSA8LSBzY2FsZShteWRhdGEpDQojcmVuZHJlIG1lcyBkb25uw6llcyBkYXRhZnJhbWUNCm15ZGF0YSA8LSBkYXRhLmZyYW1lKG15ZGF0YSkNCm15ZGF0YQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeSgnY2FyZXQnKQ0KIyBTcGxpdCB0aGUgZGF0YXNldCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMNCnNldC5zZWVkKDEyMykNCnRyYWluSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihteWRhdGEkcmF0aW5nLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQp0cmFpbkRhdGEgPC0gbXlkYXRhW3RyYWluSW5kZXgsIF0NCnRlc3REYXRhIDwtIG15ZGF0YVstdHJhaW5JbmRleCwgXQ0KDQojIFRyYWluIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBvbiB0aGUgdHJhaW5pbmcgc2V0DQpsbV9tb2RlbCA8LSB0cmFpbihyYXRpbmcgfiAuICwgZGF0YSA9IHRyYWluRGF0YSwgbWV0aG9kID0gImxtIikNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldCB1c2luZyB0aGUgdHJhaW5lZCBtb2RlbA0KcHJlZGljdGlvbnMgPC0gcHJlZGljdChsbV9tb2RlbCwgbmV3ZGF0YSA9IHRlc3REYXRhKQ0KDQojIENhbGN1bGF0ZSB0aGUgcm9vdCBtZWFuIHNxdWFyZWQgZXJyb3IgKFJNU0UpIHRvIGV2YWx1YXRlIG1vZGVsIHBlcmZvcm1hbmNlDQpybXNlIDwtIHNxcnQobWVhbigodGVzdERhdGEkcmF0aW5nIC0gcHJlZGljdGlvbnMpXjIpKQ0KDQojIFByaW50IHRoZSBSTVNFIHZhbHVlDQpwcmludChwYXN0ZTAoIlJNU0U6ICIsIHJtc2UpKQ0KDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIFNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cyAoNzAvMzAgc3BsaXQpDQpzZXQuc2VlZCgxMjMpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQ0KdHJhaW5faW5kZXggPC0gc2FtcGxlKDE6bnJvdyhteWRhdGEpLCBzaXplID0gcm91bmQoMC43ICogbnJvdyhteWRhdGEpKSwgcmVwbGFjZSA9IEZBTFNFKQ0KdHJhaW5fZGF0YSA8LSBteWRhdGFbdHJhaW5faW5kZXgsIF0NCnRlc3RfZGF0YSA8LSBteWRhdGFbLXRyYWluX2luZGV4LCBdDQoNCiMgRml0IGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdG8gdGhlIHRyYWluaW5nIGRhdGEgdG8gcHJlZGljdCByYXRpbmcNCm1vZGVsIDwtIGxtKHJhdGluZyB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhKQ0KDQojIFByZWRpY3QgdGhlIHJhdGluZyB2YWx1ZXMgZm9yIHRoZSB0ZXN0aW5nIGRhdGEgdXNpbmcgdGhlIHRyYWluZWQgbW9kZWwNCnByZWRpY3Rpb25zIDwtIHByZWRpY3QobW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpDQoNCiMgQ2FsY3VsYXRlIHRoZSBSTVNFIGZvciB0aGUgdGVzdGluZyBkYXRhDQpybXNlIDwtIHNxcnQobWVhbigocHJlZGljdGlvbnMgLSB0ZXN0X2RhdGEkcmF0aW5nKV4yKSkNCg0KIyBQbG90IHRoZSBsZWFybmluZyBwcm9jZXNzIGZvciB0aGUgdHJhaW5pbmcgZGF0YQ0KbGVhcm5pbmdfY3VydmUgPC0gZGF0YS5mcmFtZSgNCiAgaXRlcmF0aW9uID0gMTpsZW5ndGgobW9kZWwkcmVzaWR1YWxzKSwNCiAgcm1zZSA9IHNxcnQoY3Vtc3VtKG1vZGVsJHJlc2lkdWFsc14yKS9sZW5ndGgobW9kZWwkcmVzaWR1YWxzKSkNCikNCg0KZ2dwbG90KGxlYXJuaW5nX2N1cnZlLCBhZXMoeCA9IGl0ZXJhdGlvbiwgeSA9IHJtc2UpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJibHVlIikgKw0KICBsYWJzKHRpdGxlID0gIkxlYXJuaW5nIGN1cnZlIiwgeCA9ICJJdGVyYXRpb24iLCB5ID0gIlJNU0UiKQ0KIyBQcmludCB0aGUgUk1TRSBmb3IgdGhlIHRlc3RpbmcgZGF0YQ0KcHJpbnQocGFzdGUoIlJNU0Ugb24gdGVzdGluZyBkYXRhOiIsIHJtc2UpKQ0KDQoNCiMgSW5pdGlhbGlzZXIgbGVzIHZlY3RldXJzIGRlIHN0b2NrYWdlIHBvdXIgbGEgcGVyZm9ybWFuY2UgZCdlbnRyYcOubmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCnRyYWluX3BlcmYgPC0gYygpDQp2YWxfcGVyZiA8LSBjKCkNCg0KIyBEw6lmaW5pciBsZXMgdGFpbGxlcyBkZSBzb3VzLWVuc2VtYmxlcyBwb3VyIGwnZW50cmHDrm5lbWVudA0KdHJhaW5fc2l6ZXMgPC0gc2VxKDIsIG5yb3codHJhaW4pLCBieSA9IDIpDQoNCiMgVHJhY2VyIGxhIGNvdXJiZSBkJ2FwcHJlbnRpc3NhZ2UNCnBsb3QodHJhaW5fc2l6ZXMsIHRyYWluX3BlcmYsIHR5cGUgPSAibCIsIGNvbCA9ICJibHVlIiwgeGxhYiA9ICJUYWlsbGUgZGUgbCdlbnNlbWJsZSBkJ2VudHJhw65uZW1lbnQiLCB5bGFiID0gIlJNU0UiKQ0KbGluZXModHJhaW5fc2l6ZXMsIHZhbF9wZXJmLCB0eXBlID0gImwiLCBjb2wgPSAicmVkIikNCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQgPSBjKCJFbnRyYcOubmVtZW50IiwgInRlc3QiKSwgY29sID0gYygiYmx1ZSIsICJyZWQiKSwgbHR5ID0gMSkNCmBgYA0KDQoNCg==